package zxj.weixin.mp.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import zxj.weixin.mp.domain.message.CardCheckMsg;
import zxj.weixin.mp.domain.message.PoiCheckNotifyEventMsg;
import zxj.weixin.mp.domain.message.SubscribeEventMsg;
import zxj.weixin.mp.domain.message.TextMsg;
import zxj.weixin.mp.domain.message.UserDelCardMsg;
import zxj.weixin.mp.domain.message.UserGetCardMsg;
import zxj.weixin.mp.mongo.TextMsgDao;
import zxj.weixin.utils.MapUtils;
import zxj.weixin.utils.RedisStringUtils;
import zxj.weixin.utils.XmlUtils;

/**
 * 微信公消息Service
 * 
 * @author zhuxuejiang
 *
 */
@Service
public class MessageService {

	private static final Logger LOGGER = LoggerFactory.getLogger(MessageService.class);

	/* 收普通消息 */

	/** 消息类型-文本消息 */
	private static final String MSG_TYPE_TEXT = "text";

	/** 消息类型-图片消息 */
	private static final String MSG_TYPE_IMAGE = "image";

	/** 消息类型-语音消息 */
	private static final String MSG_TYPE_VOICE = "voice";

	/** 消息类型-视频消息 */
	private static final String MSG_TYPE_VIDEO = "video";

	/** 消息类型-小视频消息 */
	private static final String MSG_TYPE_SHORT_VIDEO = "shortvideo";

	/** 消息类型-地理位置消息 */
	private static final String MSG_TYPE_LOCATION = "location";

	/** 消息类型-链接消息 */
	private static final String MSG_TYPE_LINK = "link";

	/* 事件推送 */

	/** 消息类型-事件推送消息 */
	private static final String MSG_TYPE_EVENT = "event";

	/** 事件类型-订阅 ；扫描带参数二维码事件-用户未关注时，进行关注后的事件推送 */
	private static final String EVENT_TYPE_SUBSCRIBE = "subscribe";

	/** 事件类型-取消订阅 */
	private static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";

	/** 事件类型-扫描带参数二维码事件-用户已关注时的事件推送 */
	private static final String EVENT_TYPE_SCAN = "scan";

	/** 事件类型-上报地理位置事件 */
	private static final String EVENT_TYPE_LOCATION = "location";

	/** 事件类型-自定义菜单事件-点击菜单拉取消息时的事件推送 */
	private static final String EVENT_TYPE_CLICK = "click";

	/** 事件类型-自定义菜单事件-点击菜单跳转链接时的事件推送 */
	private static final String EVENT_TYPE_VIEW = "view";

	/* 卡券事件推送 */

	/** 事件类型-微信卡券审核事件推送-卡券通过审核 */
	private static final String EVENT_CARD_PASS_CHECK = "card_pass_check";

	/** 事件类型-微信卡券审核事件推送-卡券未通过审核 */
	private static final String EVENT_CARD_NOT_PASS_CHECK = "card_not_pass_check";

	/** 事件类型-微信卡券领取事件推送 */
	private static final String EVENT_USER_GET_CARD = "user_get_card";

	/** 事件类型-微信卡券删除事件推送 */
	private static final String EVENT_USER_DEL_CARD = "user_del_card";

	/** 事件类型-进入会员卡事件推送 */
	private static final String EVENT_USER_VIEW_CARD = "user_view_card";

	/** 事件类型-会员卡激活事件推送 */
	private static final String EVENT_SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info";

	/* 门店事件推送 */

	/** 事件类型-微信门店审核事件推送 */
	private static final String EVENT_POI_CHECK_NOTIFY = "poi_check_notify";

	private static final String KEY_MSG_ID_SET = "wx_msg_id_set.";
	private static final long TIMEOUT_MSG_ID_SET = 60;

	@Autowired
	private TextMsgDao textMsgDao;

	@Autowired
	private BusinessService businessService;

	@Autowired
	private CardService cardService;

	@Autowired
	private UserService userService;

	@Autowired
	private RedisStringUtils redisStringUtils;

	/**
	 * 处理微信消息
	 * 
	 * @param inputStream 输入流
	 * @return 回复消息（XML字符串）
	 * @throws Exception
	 */
	public String processMessage(InputStream inputStream) throws Exception {
		Map<String, Object> msgMap = XmlUtils.xml2Map(inputStream);

		if (null == msgMap) {
			return null;
		}

		String msgType = (String) msgMap.get("MsgType");
		if (null == msgType) {
			return null;
		}

		if (msgType.equalsIgnoreCase(MSG_TYPE_EVENT)) {
			// 处理事件推送消息
			return this.processEventMessage(msgMap);
		}

		// 处理普通消息
		return this.processNormalMessage(msgMap);
	}

	/**
	 * 处理普通消息
	 * 
	 * @param msgMap 微信消息Map
	 * @return 回复消息（XML字符串）
	 * @throws IOException
	 */
	public String processNormalMessage(Map<String, Object> msgMap) throws IOException {
		String toUserName = (String) msgMap.get("ToUserName");
		String msgType = (String) msgMap.get("MsgType");
		String msgId = (String) msgMap.get("MsgId");

		if (StringUtils.isBlank(toUserName) || StringUtils.isBlank(msgId)) {
			return null;
		}

		// 判断是否为重复的消息
		if (this.existMsgId(toUserName, msgId)) {
			return null;
		}

		String reply = null;
		if (msgType.equalsIgnoreCase(MSG_TYPE_TEXT)) {
			LOGGER.debug("处理文本消息");
			TextMsg textMsg = (TextMsg) MapUtils.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap), TextMsg.class);
			if (null != textMsg) {
				textMsg.setCreateTime(textMsg.getCreateTime());
				textMsgDao.save(textMsg);
			}

			TextMsg replyMsg = new TextMsg();
			replyMsg.setToUserName(textMsg.getFromUserName());
			replyMsg.setFromUserName(textMsg.getToUserName());
			replyMsg.setCreateTime(System.currentTimeMillis() / 1000);
			replyMsg.setMsgType(MSG_TYPE_TEXT);
			replyMsg.setContent("文本消息：" + textMsg.getContent());
			textMsgDao.save(replyMsg);

			Map<String, Object> replyMap = MapUtils.object2Map(replyMsg);
			replyMap.remove("msgId");
			reply = XmlUtils.map2XmlString(MapUtils.keyLowerCamel2UpperCamel(replyMap));
		} else if (msgType.equalsIgnoreCase(MSG_TYPE_IMAGE)) {
			LOGGER.debug("处理图片消息");
		} else if (msgType.equalsIgnoreCase(MSG_TYPE_VOICE)) {
			LOGGER.debug("处理语音消息");
		} else if (msgType.equalsIgnoreCase(MSG_TYPE_SHORT_VIDEO)) {
			LOGGER.debug("处理视频消息");
		} else if (msgType.equalsIgnoreCase(MSG_TYPE_VIDEO)) {
			LOGGER.debug("处理小视频消息");
		} else if (msgType.equalsIgnoreCase(MSG_TYPE_LOCATION)) {
			LOGGER.debug("处理地理位置消息");
		} else if (msgType.equalsIgnoreCase(MSG_TYPE_LINK)) {
			LOGGER.debug("处理链接消息");
		}

		this.removeMsgId(toUserName, msgId);
		return reply;
	}

	/**
	 * 处理事件推送消息
	 * 
	 * @param msgMap
	 * @return 回复消息（XML字符串）
	 * @throws Exception
	 */
	public String processEventMessage(Map<String, Object> msgMap) throws Exception {
		String toUserName = (String) msgMap.get("ToUserName");
		String fromUserName = (String) msgMap.get("FromUserName");
		String createTime = (String) msgMap.get("CreateTime");

		if (StringUtils.isBlank(toUserName) || StringUtils.isBlank(fromUserName) || StringUtils.isBlank(createTime)) {
			return null;
		}

		// 判断是否为重复的消息
		String msgId = fromUserName + createTime;
		if (this.existMsgId(toUserName, msgId)) {
			return null;
		}

		String reply = null;
		String event = (String) msgMap.get("Event");
		if (event.equalsIgnoreCase(EVENT_TYPE_SUBSCRIBE)) {
			LOGGER.debug("处理订阅事件或扫描带参数二维码事件-用户未关注时，进行关注后的事件推送");
			userService.subscribe((SubscribeEventMsg) MapUtils.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap),
					SubscribeEventMsg.class));

			TextMsg replyMsg = new TextMsg();
			replyMsg.setToUserName(fromUserName);
			replyMsg.setFromUserName(toUserName);
			replyMsg.setCreateTime(System.currentTimeMillis());
			replyMsg.setMsgType(MSG_TYPE_TEXT);
			replyMsg.setContent("订阅：" + fromUserName);
			textMsgDao.save(replyMsg);

			reply = XmlUtils.map2XmlString(MapUtils.keyLowerCamel2UpperCamel(MapUtils.object2Map(replyMsg)));
		} else if (event.equalsIgnoreCase(EVENT_TYPE_UNSUBSCRIBE)) {
			LOGGER.debug("处理取消订阅事件");
			userService.unsubscribe((SubscribeEventMsg) MapUtils.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap),
					SubscribeEventMsg.class));
		} else if (event.equalsIgnoreCase(EVENT_TYPE_SCAN)) {
			LOGGER.debug("处理扫描带参数二维码事件-用户已关注时的事件推送");
		} else if (event.equalsIgnoreCase(EVENT_TYPE_LOCATION)) {
			LOGGER.debug("处理上报地理位置事件");
		} else if (event.equalsIgnoreCase(EVENT_TYPE_CLICK)) {
			LOGGER.debug("处理自定义菜单事件-点击菜单拉取消息时的事件推送");
		} else if (event.equalsIgnoreCase(EVENT_TYPE_VIEW)) {
			LOGGER.debug("处理自定义菜单事件-点击菜单跳转链接时的事件推送");
		} else if (event.equalsIgnoreCase(EVENT_CARD_PASS_CHECK)) {
			LOGGER.debug("处理微信卡券审核事件推送-卡券通过审核");
			cardService.checkNotify(
					(CardCheckMsg) MapUtils.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap), CardCheckMsg.class));
		} else if (event.equalsIgnoreCase(EVENT_CARD_NOT_PASS_CHECK)) {
			LOGGER.debug("处理微信卡券审核事件推送-卡券未通过审核");
			cardService.checkNotify(
					(CardCheckMsg) MapUtils.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap), CardCheckMsg.class));
		} else if (event.equalsIgnoreCase(EVENT_USER_GET_CARD)) {
			LOGGER.debug("处理微信卡券领取事件推送");
			cardService.userGetCardNotify((UserGetCardMsg) MapUtils
					.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap), UserGetCardMsg.class));
		} else if (event.equalsIgnoreCase(EVENT_USER_DEL_CARD)) {
			LOGGER.debug("处理微信卡券删除事件推送");
			cardService.userDelCardNotify((UserDelCardMsg) MapUtils
					.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap), UserDelCardMsg.class));
		} else if (event.equalsIgnoreCase(EVENT_USER_VIEW_CARD)) {
			LOGGER.debug("处理进入会员卡事件推送");
			LOGGER.debug(msgMap.toString());
		} else if (event.equalsIgnoreCase(EVENT_SUBMIT_MEMBERCARD_USER_INFO)) {
			LOGGER.debug("处理会员卡激活事件推送");
			LOGGER.debug(msgMap.toString());
		} else if (event.equalsIgnoreCase(EVENT_POI_CHECK_NOTIFY)) {
			LOGGER.debug("处理微信门店审核事件推送");
			businessService.checkNotify((PoiCheckNotifyEventMsg) MapUtils
					.map2Object(MapUtils.keyUpperCamel2LowerCamel(msgMap), PoiCheckNotifyEventMsg.class));
		} else {
			LOGGER.debug("未处理的事件类型：" + event);
			LOGGER.debug(msgMap.toString());
		}

		this.removeMsgId(toUserName, msgId);
		return reply;
	}

	/**
	 * 检查消息ID是否存在
	 * 
	 * @param wxId  微信原始ID
	 * @param msgId 微信消息ID
	 * @return true-存在；false-不存在
	 */
	private boolean existMsgId(String wxId, String msgId) {
		String key = KEY_MSG_ID_SET + wxId;

		Boolean exist = redisStringUtils.opsForSetIsMember(key, msgId);
		if (exist) {
			return true;
		}

		redisStringUtils.opsForSetAdd(key, msgId);
		redisStringUtils.expire(key, TIMEOUT_MSG_ID_SET);

		return false;
	}

	/**
	 * 移除消息ID
	 * 
	 * @param wxId  微信原始ID
	 * @param msgId 微信消息ID
	 */
	private void removeMsgId(String wxId, String msgId) {
		String key = KEY_MSG_ID_SET + wxId;
		redisStringUtils.opsForSetRemove(key, msgId);
	}

}
